package com.gitee.qdbp.general.thirdpart.biz.wechat.service;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.general.thirdpart.api.trade.model.BasePayParams;
import com.gitee.qdbp.general.thirdpart.api.trade.model.OrderData;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PaidNotify;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PaidResponse;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayData;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayParams;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayResponse;
import com.gitee.qdbp.general.thirdpart.api.wechat.model.WechatPayee;
import com.gitee.qdbp.general.thirdpart.api.wechat.model.WechatPrepayParams;
import com.gitee.qdbp.general.thirdpart.api.wechat.service.IWechatTradeService;
import com.gitee.qdbp.general.thirdpart.biz.HttpUtils;
import com.gitee.qdbp.general.thirdpart.biz.PayUtils;
import com.gitee.qdbp.general.thirdpart.biz.trade.common.BaseTradeService;
import com.gitee.qdbp.general.thirdpart.biz.wechat.model.WechatConfig;
import com.gitee.qdbp.general.thirdpart.enums.TradeState;
import com.gitee.qdbp.general.thirdpart.error.PaymentErrorCode;
import com.gitee.qdbp.general.trade.enums.PayChannel;
import com.gitee.qdbp.general.trade.enums.PaySource;
import com.gitee.qdbp.tools.utils.DateTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.RandomTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 微信交易接口<br>
 * 微信支付, 先创建预付单, 再对预付单发起支付<br>
 * 预付单一旦创建就不能修改; 预付单关闭后再对同一商家订单号创建预付单还是会报订单号重复<br>
 * 目前的实现是每次支付前先查询是否存在旧的支付订单号, 如果存在且未支付则先关闭;<br>
 * 然后生成新的支付订单号(等于商家订单号加上2位序号)进行支付
 * 
 * @author zhaohuihua
 * @version 171018
 */
public class WechatTradeService extends BaseTradeService implements IWechatTradeService {

    @Autowired
    protected WechatConfig config;

    public WechatTradeService() {
        super("WechatPay");
    }

    /**
     * 获取支付参数
     * 
     * @param params 订单信息, 如果为空则取默认收款人信息
     * @return
     */
    /** {@inheritDoc} **/
    @Override
    public Map<String, Object> getPaymentParams(PrepayParams params) throws ServiceException {

        WechatPayee def = getDefaultWechatPayee(params);
        WechatPayee payee;
        if (params == null) {
            payee = def;
        } else {
            // 检查订单, 检查支付状态, ...
            PrepayData prepayData = this.doPrepayCheckOrder(params);
            // 查询收款人信息
            payee = this.doGetWechatPayee(prepayData, params.getPaySource(), def);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("test", config.isPaymentTest());
        map.put("authUrl", config.getAuthorizeApiUrl());
        map.put("appId", payee.getAppId());
        return map;
    }

    /**
     * 电脑扫码支付<br>
     * https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1<br>
     * H5网页支付<br>
     * https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1<br>
     * 公众号支付<br>
     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1<br>
     * 
     * @param params
     * @return
     * @throws ServiceException
     */
    /** {@inheritDoc} **/
    @Override
    public Map<String, Object> createPrepay(WechatPrepayParams params) throws ServiceException {
        params.setPayChannel(PayChannel.WECHAT);
        if (VerifyTools.isBlank(params.getPaySource())) {
            log.trace("Failed to createPrepay. PaySource is null");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (params.getPaySource() == PaySource.WX) {
            if (!config.isPaymentTest() && VerifyTools.isBlank(params.getCode())) {
                log.error("Failed to createPrepay. PaySource=WX then code is required.");
                throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
            }
        }

        if (log.isInfoEnabled()) {
            String test = config.isPaymentTest() ? "Test" : "";
            log.info("[WechatPay]{}CreatePagePayFormParams: {}", test, JsonTools.newlineLogs(params));
        }

        if (VerifyTools.isBlank(params.getNotifyUrl())) {
            params.setNotifyUrl(config.getMerchantNotifyUrl());
        }

        // 检查订单, 检查支付状态, ...
        PrepayData prepayData = this.doPrepayCheckOrder(params);

        // 检查库存, ...
        this.doPrepayCheckBusiness(prepayData);

        // 查询收款人信息
        WechatPayee payee = this.doGetWechatPayee(prepayData, params.getPaySource(), getDefaultWechatPayee(prepayData));

        // 生成支付单号
        String oldPaymentId = prepayData.getPaymentId();
        String newPaymentId = this.doGeneratePaymentId(prepayData);

        prepayData.setPayChannel(params.getPayChannel());
        prepayData.setPaySource(params.getPaySource());
        prepayData.setOrderType(params.getOrderType());
        prepayData.setPayeeAccount(payee.getAppId());
        prepayData.setPaymentId(newPaymentId);

        if (config.isPaymentTest()) { // 测试模式
            Map<String, Object> form = this.doHandleTestPay(params, prepayData, payee);
            if (log.isInfoEnabled()) {
                String m = "[WechatPay]TestPaySuccess. {}";
                log.info(m, JsonTools.newlineLogs(prepayData, form));
            }
            return form;
        } else {

            if (VerifyTools.isNotBlank(oldPaymentId)) {
                checkAndCloseOldPrepay(oldPaymentId, prepayData, payee);
            }

            Map<String, String> prepay;
            // 创建预付单
            try {
                prepay = createPrepay(params, prepayData, payee);
            } catch (ServiceException e) {
                PrepayResponse response = new PrepayResponse();
                response.put("code", e.getCode());
                response.put("message", e.getMessage());
                this.doPrepayError(prepayData, response);
                throw e;
            }

            // 得到预付id
            String prepayId = prepay.get("prepay_id");

            // 预付单创建成功, 修改付款状态为支付中, 锁定库存, ...
            PrepayResponse response = new PrepayResponse();
            response.setVoucherCode(prepayId);
            response.setExtra(PayUtils.toKeyObjectMap(prepay));
            this.doPrepaySuccess(prepayData, response);

            if (params.getPaySource() == PaySource.WX) {
                Map<String, String> result = new HashMap<>();
                result.put("appId", payee.getAppId());
                result.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
                result.put("nonceStr", RandomTools.generateString(16));
                result.put("package", "prepay_id=" + prepayId);
                result.put("signType", "MD5");
                result.put("paySign", WechatUtils.generateSignature(result, payee.getEncryptKey(), false));
                result.put("returnUrl", WechatUtils.urlAppendParams(params.getReturnUrl(), prepayId, prepayData));

                if (log.isInfoEnabled()) {
                    String m = "[WechatPay]CreateWxPrepaySuccess, prepayId={}. {}";
                    log.info(m, prepayId, JsonTools.newlineLogs(prepayData, prepay, result));
                }
                return PayUtils.toKeyObjectMap(result);
            } else if (params.getPaySource() == PaySource.H5) {
                Map<String, Object> result = new HashMap<>();
                result.put("appId", payee.getAppId());
                result.put("prepayId", prepayId);
                result.put("expireTime", DateTools.addHour(new Date(), 2));
                result.put("orderId", prepayData.getOrderId());
                result.put("paymentId", prepayData.getPaymentId());
                result.put("orderType", prepayData.getOrderType());
                result.put("orderTitle", prepayData.getOrderTitle());
                result.put("orderContent", prepayData.getOrderContent());
                result.put("totalAmount", prepayData.getTotalAmount());
                result.put("mwebUrl", prepay.get("mweb_url"));
                result.put("returnUrl", WechatUtils.urlAppendParams(params.getReturnUrl(), prepayId, prepayData));

                if (log.isInfoEnabled()) {
                    String m = "[WechatPay]CreateH5PrepaySuccess, prepayId={}.{}";
                    log.info(m, prepayId, JsonTools.newlineLogs(prepayData, prepay, result));
                }
                return result;
            } else if (params.getPaySource() == PaySource.PC) {
                Map<String, Object> result = new HashMap<>();
                result.put("appId", payee.getAppId());
                result.put("prepayId", prepayId);
                result.put("expireTime", DateTools.addHour(new Date(), 2)); // code_url有效期为2小时
                result.put("orderId", prepayData.getOrderId());
                result.put("paymentId", prepayData.getPaymentId());
                result.put("orderType", prepayData.getOrderType());
                result.put("orderTitle", prepayData.getOrderTitle());
                result.put("orderContent", prepayData.getOrderContent());
                result.put("totalAmount", prepayData.getTotalAmount());
                result.put("codeUrl", prepay.get("code_url"));
                result.put("codeImage", WechatUtils.createQrCodeImage(prepay.get("code_url"), 300, 0));
                result.put("returnUrl", WechatUtils.urlAppendParams(params.getReturnUrl(), prepayId, prepayData));

                if (log.isInfoEnabled()) {
                    String m = "[WechatPay]CreateQrPrepaySuccess, prepayId={}.{}";
                    log.info(m, prepayId, JsonTools.newlineLogs(prepayData, prepay, result));
                }
                return result;
            } else {
                throw new ServiceException(ResultCode.UNSUPPORTED_OPERATION);
            }
        }
    }

    /** 支付成功后, 微信回调处理 **/
    /** {@inheritDoc} **/
    @Override
    public String notifyHandle(String xml) {
        if (log.isInfoEnabled()) {
            log.info("[WechatPay]NotifyHandleParams:\n{}", JsonTools.toJsonString(xml));
        }

        String format = "<xml><return_code><![CDATA[%s]]></return_code><return_msg><![CDATA[%s]]></return_msg></xml>";

        String result;
        try {
            doNotifyHandle(xml);
            result = String.format(format, "SUCCESS", "OK");
        } catch (ServiceException e) {
            result = String.format(format, "FAIL", e.getCode());
        } catch (Throwable e) {
            String m = "[WechatPay]NotifyHandleError, {}\n\t{}";
            log.error(m, e.toString(), JsonTools.toJsonString(xml), e);
            result = String.format(format, "FAIL", "ERROR");
        }

        if (log.isInfoEnabled()) {
            log.info("[WechatPay]NotifyHandleDone: \n\t{}", result);
        }
        return result;
    }

    // 微信回调处理
    protected void doNotifyHandle(String xml) throws ServiceException {
        if (VerifyTools.isBlank(xml)) {
            log.trace("Failed to doNotifyHandle. xml is null");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
        // <xml>
        //    <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
        //    <attach><![CDATA[支付测试]]></attach>
        //    <bank_type><![CDATA[CFT]]></bank_type>
        //    <fee_type><![CDATA[CNY]]></fee_type>
        //    <is_subscribe><![CDATA[Y]]></is_subscribe>
        //    <mch_id><![CDATA[10000100]]></mch_id>
        //    <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
        //    <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
        //    <out_trade_no><![CDATA[1409811653]]></out_trade_no>
        //    <result_code><![CDATA[SUCCESS]]></result_code>
        //    <return_code><![CDATA[SUCCESS]]></return_code>
        //    <sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
        //    <sub_mch_id><![CDATA[10000100]]></sub_mch_id>
        //    <time_end><![CDATA[20140903131540]]></time_end>
        //    <total_fee>1</total_fee>
        //    <trade_type><![CDATA[JSAPI]]></trade_type>
        //    <transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
        // </xml>

        Map<String, String> params = WechatUtils.doXmlParse(xml);

        PaidNotify notify = new PaidNotify();
        notify.setPayChannel(PayChannel.WECHAT);
        notify.setPaymentId(params.get("out_trade_no"));
        this.fillPassbackParams(notify, params.get("attach"));

        // 查询订单信息
        OrderData order = doPaidQueryOrder(notify);
        if (order == null) {
            if (log.isTraceEnabled()) {
                String m = "[WechatPay]PaidQueryOrder return null, exit process!";
                log.trace(m, JsonTools.newlineLogs(notify));
            }
            return;
        }

        // 查询收款人信息
        WechatPayee payee = doGetWechatPayee(order, notify, this.getDefaultWechatPayee(order));

        // 验证签名
        try {
            WechatUtils.checkSignature(params, payee.getEncryptKey(), false, "sign");
        } catch (ServiceException e) {
            if (log.isErrorEnabled()) {
                log.error("[WechatPay]SignatureCheckError: \n\t{}", JsonTools.toJsonString(params));
            }
            throw e;
        }

        Double totalAmount = null;
        if (params.containsKey("total_fee")) {
            totalAmount = PayUtils.parseDouble(params.get("total_fee")) / 100D;
        }
        Double receiptAmount = null;
        if (params.containsKey("settlement_total_fee")) {
            receiptAmount = PayUtils.parseDouble(params.get("settlement_total_fee")) / 100D;
        } else {
            receiptAmount = totalAmount;
        }

        String resultCode = params.get("result_code");
        TradeState tradeState = "SUCCESS".equals(resultCode) ? TradeState.TRADE_SUCCESS : TradeState.TRADE_PAY_FAIL;

        PaidResponse response = new PaidResponse();
        response.setOrderId(order.getOrderId()); // 订单编号
        response.setPaymentId(notify.getPaymentId()); // 支付单编号
        response.setTotalAmount(totalAmount); // 订单金额(元)
        response.setReceiptAmount(receiptAmount); // 实收金额(元)
        response.setPayeeId(payee.getAppId()); // 收款人编号
        response.setPayerId(params.get("openid")); // 付款人编号
        response.setPayTime(PayUtils.parseDate(params.get("time_end"))); // 支付完成时间, yyyyMMddHHmmss
        response.setTradeState(tradeState); // 交易状态
        response.setTradeSerialCode(params.get("transaction_id")); // 交易流水号
        response.setExtra(PayUtils.toKeyObjectMap(params));

        // 修改付款状态为支付成功, 核减库存, ...
        this.doNotifyHandleSuccess(order, notify, response);
    }

    // 支付测试模式
    protected Map<String, Object> doHandleTestPay(WechatPrepayParams params, PrepayData prepayData, WechatPayee payee)
            throws ServiceException {
        String tradeType = getTradeType(params.getPaySource());
        Date curr = new Date();
        String prepayId = DateTools.toNormativeString(curr, true) + RandomTools.generateNumber(8);
        {
            Map<String, Object> testResponse = new HashMap<>();
            testResponse.put("out_trade_no", prepayData.getPaymentId());
            testResponse.put("return_code", "SUCCESS");
            testResponse.put("return_msg", "OK");
            testResponse.put("result_code", "SUCCESS");
            testResponse.put("trade_type", tradeType);
            testResponse.put("appid", payee.getAppId());
            testResponse.put("mch_id", payee.getMerchantId());
            testResponse.put("prepay_id", prepayId);
            // 预付单创建成功, 修改付款状态为支付中, 锁定库存, ...
            PrepayResponse response = new PrepayResponse();
            response.setVoucherCode(prepayId);
            response.setExtra((JSONObject) JSON.toJSON(testResponse));
            this.doPrepaySuccess(prepayData, response);
        }

        PaidNotify notify = new PaidNotify();
        notify.setOrderType(params.getOrderType());
        notify.setPayChannel(params.getPayChannel());
        notify.setPaySource(params.getPaySource());
        notify.setPaymentId(prepayData.getPaymentId());

        // 查询订单信息
        OrderData order = this.doPaidQueryOrder(notify);
        if (order != null) {
            PaidResponse response = new PaidResponse();
            response.setOrderId(prepayData.getOrderId()); // 订单编号
            response.setPaymentId(notify.getPaymentId()); // 支付单编号
            response.setTotalAmount(prepayData.getTotalAmount()); // 订单金额(元)
            response.setReceiptAmount(0D); // 实收金额(元)
            response.setPayeeId(payee.getAppId()); // 收款人编号
            response.setPayTime(curr); // 付款时间, yyyy-MM-dd HH:mm:ss
            response.setTradeState(TradeState.TRADE_SUCCESS); // 交易状态
            response.setTradeSerialCode("TEST" + RandomTools.generateNumber(12));
            // 修改付款状态, 核减库存/自动发货, ...
            this.doNotifyHandleSuccess(order, notify, response);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("test", true);
        result.put("appId", payee.getAppId());
        result.put("prepayId", prepayId);
        result.put("orderId", prepayData.getOrderId());
        result.put("paymentId", prepayData.getPaymentId());
        result.put("orderType", prepayData.getOrderType());
        result.put("orderTitle", prepayData.getOrderTitle());
        result.put("orderContent", prepayData.getOrderContent());
        result.put("totalAmount", prepayData.getTotalAmount());
        result.put("returnUrl", WechatUtils.urlAppendParams(params.getReturnUrl(), prepayId, prepayData));
        return result;
    }

    // 每次支付前先查询是否存在旧的支付订单号, 如果存在且未支付则先关闭
    private void checkAndCloseOldPrepay(String oldPayOrderId, PrepayData payment, WechatPayee payee)
            throws ServiceException {

        if (VerifyTools.isBlank(oldPayOrderId)) {
            return;
        }

        Map<String, String> older = queryPrepayByPayOrderId(oldPayOrderId, payee);
        if (older == null) {
            return;
        }

        String tradeState = older.get("trade_state");
        if ("SUCCESS".equals(tradeState) || "REFUND".equals(tradeState)) { // REFUND:转入退款
            log.warn("[WechatPay]TradeAlreadyPaid: {}\n\t{}", tradeState, JsonTools.toJsonString(payment));
            throw new ServiceException(PaymentErrorCode.PAYMENT_TRADE_STATE_PAID);
        } else if ("USERPAYING".equals(tradeState)) {
            log.warn("[WechatPay]TradeAlreadyPaying: {}\n\t{}", tradeState, JsonTools.toJsonString(payment));
            throw new ServiceException(PaymentErrorCode.PAYMENT_TRADE_STATE_PAYING);
        } else if ("NOTPAY".equals(tradeState)) { // NOTPAY:未支付
            // 先关闭原先的预付单
            closePrepayByPayOrderId(oldPayOrderId, payee);
        } else { // CLOSED:已关闭, REVOKED:已撤销, PAYERROR:支付失败
            // nothing
        }
    }

    // 创建预付单
    private Map<String, String> createPrepay(WechatPrepayParams params, PrepayData data, WechatPayee payee)
            throws ServiceException {

        String openid = null;
        if (params.getPaySource() == PaySource.WX) {
            openid = getOpenId(params.getCode(), payee);
        }

        // 订单金额元转分
        String fen = String.valueOf((int) (data.getTotalAmount() * 100));

        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");

        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
        Map<String, String> map = new HashMap<>();
        map.put("appid", payee.getAppId());
        map.put("mch_id", payee.getMerchantId());
        map.put("nonce_str", RandomTools.generateString(16));
        String passbackParams = createPassbackParams(params);
        if (passbackParams != null) {
            map.put("attach", passbackParams);
        }
        if (params.getPaySource() != null) {
            map.put("device_info", params.getPaySource().name());
        }
        map.put("body", StringTools.clearEmoji(data.getOrderTitle())); // 微信支付竟然不支持Emoji
        map.put("out_trade_no", data.getPaymentId());
        map.put("total_fee", fen);
        map.put("spbill_create_ip", params.getIpAddress());
        map.put("time_start", df.format(data.getCreateTime())); // 订单生成时间
        map.put("time_expire", df.format(data.getExpireTime())); // 订单失效时间
        map.put("trade_type", getTradeType(params.getPaySource()));
        map.put("notify_url", params.getNotifyUrl());
        if (openid != null) {
            map.put("openid", openid);
        }
        map.put("sign", WechatUtils.generateSignature(map, payee.getEncryptKey(), false));

        String unifiedOrderUrl = config.getUnifiedOrderPayApiUrl();
        String xml = WechatUtils.toXml(map);

        // 调用微信支付统一下单接口
        String text;
        try {
            log.trace("[WechatPay]ApiUrl: {}", unifiedOrderUrl);
            log.trace("[WechatPay]Request: {}", xml);
            text = HttpUtils.post(unifiedOrderUrl, xml, WechatUtils.CHARSET);
            log.trace("[WechatPay]Response: {}", text);
        } catch (Exception e) {
            String m = "[WechatPay]Error. {}{}";
            log.error(m, e.toString(), JsonTools.newlineLogs(unifiedOrderUrl, xml), e);
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }

        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
        // <xml>
        //     <return_code><![CDATA[SUCCESS]]></return_code>
        //     <return_msg><![CDATA[OK]]></return_msg>
        //     <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
        //     <mch_id><![CDATA[10000100]]></mch_id>
        //     <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
        //     <openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid>
        //     <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
        //     <result_code><![CDATA[SUCCESS]]></result_code>
        //     <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
        //     <trade_type><![CDATA[JSAPI]]></trade_type>
        //  </xml>
        Map<String, String> result = WechatUtils.doXmlParse(text);

        if ("SUCCESS".equals(result.get("result_code"))) {
            try {
                // 检查签名
                WechatUtils.checkSignature(result, payee.getEncryptKey(), false, "sign");
                return result;
            } catch (ServiceException e) {
                throw e;
            } catch (Exception e) {
                String m = "[WechatPay]Error. {}.{}";
                log.error(m, e.toString(), JsonTools.newlineLogs(unifiedOrderUrl, xml), e);
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        } else {
            String errorCode = VerifyTools.nvl(result.get("err_code"), result.get("return_code"));
            String errorMsg = VerifyTools.nvl(result.get("err_code_des"), result.get("return_msg"));
            if ("NOTENOUGH".equals(errorCode)) {
                String m = "[WechatPay]CreatePrepayError, ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(data));
                throw new ServiceException(PaymentErrorCode.PAY_USER_NOT_SUFFICIENT_FUNDS);
            } else if ("ORDERPAID".equals(errorCode)) {
                String m = "[WechatPay]CreatePrepayError, ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(data));
                throw new ServiceException(PaymentErrorCode.PAYMENT_TRADE_STATE_PAID);
            } else if ("ORDERCLOSED".equals(errorCode)) {
                String m = "[WechatPay]CreatePrepayError, ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(data));
                throw new ServiceException(PaymentErrorCode.PAYMENT_TRADE_STATE_CLOSED);
            } else if ("签名错误".equals(errorMsg)) {
                String m = "[WechatPay]CreatePrepayError, ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(data));
                ServiceException e = new ServiceException(PaymentErrorCode.PAYMENT_SIGNATURE_ERROR);
                e.setMessage("签名错误, 请检查API密钥");
                throw e;
            } else {
                String m = "[WechatPay]CreatePrepayError, ResultCode is {}({}). {}";
                log.error(m, errorCode, errorMsg, JsonTools.newlineLogs(unifiedOrderUrl, xml, text));
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        }
    }

    // 调微信接口根据付款单查询是否存在
    private Map<String, String> queryPrepayByPayOrderId(String paymentId, WechatPayee payee) throws ServiceException {
        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
        Map<String, String> map = new HashMap<>();
        map.put("appid", payee.getAppId());
        map.put("mch_id", payee.getMerchantId());
        map.put("nonce_str", RandomTools.generateString(16));
        map.put("out_trade_no", paymentId);
        map.put("sign", WechatUtils.generateSignature(map, payee.getEncryptKey(), false));

        String queryOrderUrl = config.getUnifiedOrderQueryApiUrl();
        String xml = WechatUtils.toXml(map);

        // 调用微信支付查询接口
        String text;
        try {
            log.trace("[WechatPay]ApiUrl: {}", queryOrderUrl);
            log.trace("[WechatPay]Request: {}", xml);
            text = HttpUtils.post(queryOrderUrl, xml, WechatUtils.CHARSET);
            log.trace("[WechatPay]Response: {}", text);
        } catch (Exception e) {
            String m = "[WechatPay]Error. {}{}";
            log.error(m, e.toString(), JsonTools.newlineLogs(queryOrderUrl, xml), e);
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }

        Map<String, String> response = WechatUtils.doXmlParse(text);

        if ("SUCCESS".equals(response.get("result_code"))) {
            try {
                // 检查签名
                WechatUtils.checkSignature(response, payee.getEncryptKey(), false, "sign");
                return response;
            } catch (ServiceException e) {
                throw e;
            } catch (Exception e) {
                String m = "[WechatPay]QueryPrepayError. {}.{}";
                log.error(m, e.toString(), JsonTools.newlineLogs(queryOrderUrl, xml), e);
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        } else {
            String errorCode = VerifyTools.nvl(response.get("err_code"), response.get("return_code"));
            String errorMsg = VerifyTools.nvl(response.get("err_code_des"), response.get("return_msg"));
            if ("ORDERNOTEXIST".equals(errorCode)) {
                if (log.isTraceEnabled()) {
                    log.trace("[WechatPay]QueryPrepayError, OrderNotExist. paymentId={}", paymentId);
                }
                return null;
            } else {
                String m = "[WechatPay]QueryPrepayError, ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(queryOrderUrl, xml, text));
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        }

    }

    // 调微信接口关闭订单
    private void closePrepayByPayOrderId(String paymentId, WechatPayee payee) throws ServiceException {

        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
        Map<String, String> map = new HashMap<>();
        map.put("appid", payee.getAppId());
        map.put("mch_id", payee.getMerchantId());
        map.put("nonce_str", RandomTools.generateString(16));
        map.put("out_trade_no", paymentId);
        map.put("sign", WechatUtils.generateSignature(map, payee.getEncryptKey(), false));

        String closeOrderUrl = config.getUnifiedOrderCloseApiUrl();
        String xml = WechatUtils.toXml(map);

        // 调用微信支付关闭接口
        String text;
        try {
            log.trace("[WechatPay]ApiUrl: {}", closeOrderUrl);
            log.trace("[WechatPay]Request: {}", xml);
            text = HttpUtils.post(closeOrderUrl, xml, WechatUtils.CHARSET);
            log.trace("[WechatPay]Response: {}", text);
        } catch (Exception e) {
            String m = "[WechatPay]Error. {}{}";
            log.error(m, e.toString(), JsonTools.newlineLogs(closeOrderUrl, xml), e);
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }

        Map<String, String> result = WechatUtils.doXmlParse(text);

        if ("SUCCESS".equals(result.get("result_code"))) {
            try {
                // 检查签名
                WechatUtils.checkSignature(result, payee.getEncryptKey(), false, "sign");
            } catch (ServiceException e) {
                throw e;
            } catch (Exception e) {
                String m = "[WechatPay]Error. {}.{}";
                log.error(m, e.toString(), JsonTools.newlineLogs(closeOrderUrl, xml), e);
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        } else {
            String errorCode = VerifyTools.nvl(result.get("err_code"), result.get("return_code"));
            String errorMsg = VerifyTools.nvl(result.get("err_code_des"), result.get("return_msg"));
            if ("ORDERCLOSED".equals(errorCode)) {
                if (log.isTraceEnabled()) {
                    log.trace("[WechatPay]ClosePrepayError, OrderAlreadyClosed. paymentId={}", paymentId);
                }
            } else if ("ORDERPAID".equals(errorCode)) {
                log.warn("[WechatPay]ClosePrepayError, OrderAlreadyPaid. paymentId={}", paymentId);
                throw new ServiceException(PaymentErrorCode.PAYMENT_TRADE_STATE_PAID);
            } else {
                String m = "[WechatPay]ClosePrepayError: ResultCode is {}({}). {}";
                log.warn(m, errorCode, errorMsg, JsonTools.newlineLogs(closeOrderUrl, xml, text));
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }
        }
    }

    // 获取用户在当前公众号下的openid
    protected String getOpenId(String code, WechatPayee payee) throws ServiceException {

        String appid = payee.getAppId();
        String secret = payee.getAppSecret();
        String accessTokenUrl = config.getAccountTokenApiUrl();
        String fmt = "code=%s&appid=%s&secret=%s&grant_type=authorization_code";
        String params = String.format(fmt, code, appid, secret);

        // 获取openid
        String text;
        try {
            log.trace("[GetOpenId]ApiUrl: {}", accessTokenUrl);
            log.trace("[GetOpenId]Request: {}", params);
            text = HttpUtils.post(accessTokenUrl, params, WechatUtils.CHARSET);
            log.trace("[GetOpenId]Response: {}", text);
        } catch (Exception e) {
            String m = "[GetOpenId]Exception. {}{}";
            log.error(m, e.toString(), JsonTools.newlineLogs(accessTokenUrl, params));
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }

        // {
        //     "access_token":"ACCESS_TOKEN",
        //     "expires_in":7200,
        //     "refresh_token":"REFRESH_TOKEN",
        //     "openid":"OPENID",
        //     "scope":"SCOPE",
        //     "unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL"
        // }
        JSONObject json = JSON.parseObject(text);
        String openid = json.getString("openid");

        if (VerifyTools.isBlank(openid)) {
            String m = "[GetOpenId]Exception, openid is null. {}";
            log.warn(m, JsonTools.newlineLogs(accessTokenUrl, params, text));
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }
        return openid;
    }

    protected String getTradeType(PaySource paySource) throws ServiceException {
        if (paySource == PaySource.PC) {
            return "NATIVE";
        } else if (paySource == PaySource.H5) {
            return "MWEB";
        } else if (paySource == PaySource.WX) {
            return "JSAPI";
        } else {
            throw new ServiceException(ResultCode.UNSUPPORTED_OPERATION);
        }
    }

    /** 获取收款人信息 **/
    protected WechatPayee getDefaultWechatPayee(BasePayParams params) throws ServiceException {
        WechatPayee payee = new WechatPayee();
        payee.setAppId(config.getPayAppId());
        payee.setAppSecret(config.getPayAppSecret());
        payee.setEncryptKey(config.getPayEncryptKey());
        payee.setMerchantId(config.getPayMerchantId());
        return payee;
    }

    // 创建回调参数, S=支付来源, T=订单类型
    protected String createPassbackParams(WechatPrepayParams params) {
        Map<String, Object> map = new HashMap<>();
        if (VerifyTools.isNotBlank(params.getPaySource())) {
            map.put("S", params.getPaySource());
        }
        if (VerifyTools.isNotBlank(params.getOrderType())) {
            map.put("T", params.getOrderType());
        }
        return map.isEmpty() ? null : WechatUtils.formatPassbackParams(map);
    }

    // 支付回调时解析回调参数, S=支付来源, T=订单类型
    protected void fillPassbackParams(PaidNotify notify, String passbackString) {
        Map<String, String> passbackParams = WechatUtils.parsePassbackParams(passbackString);
        if (passbackParams.containsKey("S")) {
            notify.setPaySource(PaySource.valueOf(passbackParams.get("S")));
        }
        if (passbackParams.containsKey("T")) {
            notify.setOrderType(passbackParams.get("T"));
        }
    }

}
